package com.example.android.gestures;

import java.util.ArrayList;

/**
 * 
 * @author Gospo
 * 
 */
public class GestureParser {
	final static float alfa = (float) 4 / 5;
	final static float beta = (float) 1 / 2;
	final static float gamma = 5;

	public static Gesture parse(ArrayList<AccRecord> accRecords) {
		ArrayList<GestureParticule> gestureParticules = new ArrayList<GestureParticule>();
		ArrayList<Direction> directions = new ArrayList<Direction>();

		float[] xValues = new float[accRecords.size()];
		float[] yValues = new float[accRecords.size()];
		for (int i = 0; i < accRecords.size(); i++) {
			xValues[i] = accRecords.get(i).getX();
			yValues[i] = accRecords.get(i).getY();
		}

		decrease(xValues, mean(xValues));
		decrease(yValues, mean(yValues));

		xValues = integrate(xValues);
		yValues = integrate(yValues);

		float xMax = max(xValues) < gamma ? gamma : max(xValues);
		float xMin = min(xValues) > -gamma ? -gamma : min(xValues);
		float yMax = max(yValues) < gamma ? gamma : max(yValues);
		float yMin = min(yValues) > -gamma ? -gamma : min(yValues);

		float xUpFloor = alfa * xMax;
		float xDownFloor = alfa * xMin;
		float yUpFloor = alfa * yMax;
		float yDownFloor = alfa * yMin;

		boolean xUF = false;
		boolean xDF = false;
		boolean yUF = false;
		boolean yDF = false;
		for (int i = 0; i < accRecords.size(); i++) {
			if (xUpFloor < xValues[i] && !xUF) {
				xUF = true;
				gestureParticules.add(prepareParticle(xValues, i, Direction.R));
			} else if (xUpFloor > xValues[i] && xUF) {
				xUF = false;
			}
			if (xDownFloor > xValues[i] && !xDF) {
				xDF = true;
				gestureParticules.add(prepareParticle(xValues, i, Direction.L));
			} else if (xDownFloor < xValues[i] && xDF) {
				xDF = false;
			}

			if (yUpFloor < yValues[i] && !yUF) {
				yUF = true;
				gestureParticules.add(prepareParticle(yValues, i, Direction.U));
			} else if (yUpFloor > yValues[i] && yUF) {
				yUF = false;
			}
			if (yDownFloor > yValues[i] && !yDF) {
				yDF = true;
				gestureParticules.add(prepareParticle(yValues, i, Direction.D));
			} else if (yDownFloor < yValues[i] && yDF) {
				yDF = false;
			}
		}

		int l = gestureParticules.size();
		float[] gpIntegrals = new float[l];

		for (int i = 0; i < l; i++) {
			gpIntegrals[i] = gestureParticules.get(i).getIntegral();
		}

		float vIntMax = max(gpIntegrals);
		for (GestureParticule gp : gestureParticules) {
			if (beta * vIntMax < gp.getIntegral()) {
				directions.add(gp.getDirection());
			}
		}

		return new Gesture(directions.toArray(new Direction[directions.size()]));
	}

	static GestureParticule prepareParticle(float[] values, int i,
			Direction direction) {
		int peakIdx = findPeak(values, i, true);
		int sIdx = 0;
		int eIdx = 0;
		if (direction == Direction.R || direction == Direction.U) {
			sIdx = findDecVal(values, peakIdx, values[peakIdx] / 2, true);
			eIdx = findDecVal(values, peakIdx, values[peakIdx] / 2, false);
		}
		if (direction == Direction.L || direction == Direction.D) {
			sIdx = findIncVal(values, peakIdx, values[peakIdx] / 2, true);
			eIdx = findIncVal(values, peakIdx, values[peakIdx] / 2, false);
		}

		float[] integral = integrate(values, sIdx, eIdx + 1);
		float intVal = integral[integral.length - 1] - integral[0];
		intVal = intVal < 0 ? -intVal : intVal;

		return new GestureParticule(direction, eIdx - sIdx, values[peakIdx],
				intVal);
	}

	static int findDecVal(float[] values, int startIdx, float value, boolean o) {
		if (values.length == 0)
			return 0;
		int idx = startIdx;
		while (idx < values.length - 1 && values[idx] > value && o) {
			idx++;
		}

		while (idx > 0 && values[idx] > value && !o) {
			idx--;
		}

		return idx;
	}

	static int findIncVal(float[] values, int startIdx, float value, boolean o) {
		if (values.length == 0)
			return 0;
		int idx = startIdx;
		while (idx < values.length - 1 && values[idx] < value && o) {
			idx++;
		}

		while (idx > 0 && values[idx] < value && !o) {
			idx--;
		}

		return idx;
	}

	static int findPeak(float[] values, int startIdx, boolean o) {
		if (values.length == 0)
			return 0;
		float startVal = values[startIdx];
		float peakVal = values[startIdx];
		int peakIdx = startIdx;
		int idx = startIdx + 1;

		while (idx < values.length - 1 && values[idx] > startVal && o) {
			if (values[idx] > peakVal) {
				peakVal = values[idx];
				peakIdx = idx;
			}
			idx++;
		}

		while (idx < values.length - 1 && values[idx] < startVal && !o) {
			if (values[idx] < peakVal) {
				peakVal = values[idx];
				peakIdx = idx;
			}
			idx++;
		}

		return peakIdx;
	}

	static final void decrease(float[] values, float value) {
		for (int i = 0; i < values.length; i++) {
			values[i] -= value;
		}
	}

	static float mean(float[] values) {
		if (values.length == 0)
			return 0;
		float sum = 0;

		for (float value : values) {
			sum += value;
		}

		return sum / values.length;
	}

	static float max(float[] values) {
		if (values.length == 0)
			return 0;
		float max = values[0];

		for (int i = 1; i < values.length; i++) {
			if (values[i] > max) {
				max = values[i];
			}
		}

		return max;
	}

	static float min(float[] values) {
		if (values.length == 0)
			return 0;
		float min = values[0];

		for (int i = 1; i < values.length; i++) {
			if (values[i] < min) {
				min = values[i];
			}
		}

		return min;
	}

	static float[] filterLowPass(float[] values) {
		final float a = (float) 0.8;
		float rValue = 0;

		for (int i = 0; i < values.length; i++) {
			values[i] = rValue + a * (values[i] - rValue);
			rValue = values[i];
		}

		return values;
	}

	static float[] integrate(float[] values) {
		return integrate(values, 0, values.length);
	}

	static float[] integrate(float[] values, int s, int e) {
		int len = e - s;
		int start = s < e ? s : e;
		len = len < 0 ? -len : len;
		float[] integral = new float[len];
		integral[0] = values[start];
		for (int i = 1; i < len; i++) {
			for (int j = 0; j < i; j++) {
				integral[i] += values[start + j];
			}
		}
		return integral;
	}
}
